﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml;
using System.Collections.ObjectModel;
using System.Data;

using DbSharp.Framework.DbObject;
using System.IO;
using DbSharp.Framework.TestEngine;
using System.Diagnostics;
using DbSharp.Framework.ReleaseManage;
using DbSharp.Framework.Common;
using DbSharp.Utils.IO;

namespace DbSharp.Framework.CaseFile.Parser
{
    /// <summary>
    /// //validate xml http://support.microsoft.com/kb/307379/EN-US/
    /// </summary>
    public class CaseFileParser
    { 

        #region Private members

        string m_TestCaseFile;
        CaseFileNavigator m_FileNavigator;
        List<DbConnectionConfig> m_ListDbConnConfig = new List<DbConnectionConfig>();
        List<PerformanceTestCase> m_ListPerfTestCase = new List<PerformanceTestCase>();
        List<UnitTestCase> m_ListDataTestCase = new List<UnitTestCase>();
        List<IStatementDeclaration> m_ListStatement = new List<IStatementDeclaration>();

        #endregion


        private string FindCorrespondingSchema()
        {
            string path = PathHelper.GetAppPath();
            string schemaFile = Path.Combine(path, @"Schema\TestFileSchema2.xsd");
            return schemaFile;
        }


        public CaseFileParser(ICaseFileLoadedNotify loadedNotifier,string testCaseFile)
        { 
            var loadedResultSummary=new CaseFileLoadedReport() ;
            m_TestCaseFile = testCaseFile;
            try
            {
                if (File.Exists(testCaseFile) == false)
                {
                    string errorFmt = "File '{0}' does not exit.";
                    throw new DbSharpException(string.Format(errorFmt, testCaseFile));
                }
                m_FileNavigator = new CaseFileNavigator(testCaseFile);

                XmlIsValid = true;
                ValidationError = string.Empty;
                string schemaFile = FindCorrespondingSchema();
                if ((string.IsNullOrEmpty(schemaFile) == false)
                    && File.Exists(schemaFile))
                {
                    ValidateXmlBySchema(schemaFile);
                }
                if (XmlIsValid == true)
                    Extract(loadedResultSummary);

                loadedResultSummary.LoadedPerfCaseCount = m_ListPerfTestCase.Count;
                loadedResultSummary.LoadedUnitCaseCount = m_ListDataTestCase.Count;
                loadedResultSummary.CaseFileError=this.ValidationError;
            }
            catch (Exception ex)
            {
                XmlIsValid = false;
                loadedResultSummary.CaseFileError = ex.Message;
                Trace.TraceError(ex.ToString()) ;
            }
            finally
            {
                if (loadedNotifier != null)
                {
                    loadedNotifier.UpdateStatus(loadedResultSummary);
                }
            }
             
        }

        #region Private methods

        private void Extract(CaseFileLoadedReport loadedResultSummary)
        {
            m_ListDbConnConfig.Clear();
            m_ListStatement.Clear();
            m_ListPerfTestCase.Clear();
            m_ListDataTestCase.Clear();

            #region Extract Db Connections

            DbConnectionConfigExtractor dbExtractor;
            XElement globalSettingElement;
            globalSettingElement = m_FileNavigator.SelectGlobalSetting();
            dbExtractor = new DbConnectionConfigExtractor(m_TestCaseFile, globalSettingElement);
            m_ListDbConnConfig.AddRange(dbExtractor.ListDbConnConfig);
            ValidateDbConnCfgCount();

            #endregion

            #region Extract procedures

            ProcedureDeclarationExtractor procExtractor;
            procExtractor = new ProcedureDeclarationExtractor(m_FileNavigator.SelectProcDeclsRoot(), dbExtractor);
            if (procExtractor != null)
            {
                foreach (var item in procExtractor.ListProcDeclaration)
                {
                    m_ListStatement.Add(item);
                }
            }

            #endregion

            #region Extract Sql Statements

            SqlDeclarationExtractor sqlExtractor;
            sqlExtractor = new SqlDeclarationExtractor(m_FileNavigator.SelectSqlDeclsRoot(), dbExtractor);
            if (sqlExtractor != null)
            {
                foreach (var item in sqlExtractor.ListSqlDeclaration)
                {
                    m_ListStatement.Add(item);
                }
            }

            #endregion

            //check statements' alias 
            ValidateStatementAlias(m_ListStatement);

            #region Extract Perf cases

            PerfTestCaseExtractor perfCaseExtractor;
            perfCaseExtractor = new PerfTestCaseExtractor(m_ListStatement, m_FileNavigator.SelectPerfTestCasesRoot(), globalSettingElement);
            ValidatePerfCaseName(perfCaseExtractor.ListCase);
            m_ListPerfTestCase.AddRange(perfCaseExtractor.ListCase);
            loadedResultSummary.TotalPerfCaseCount = m_ListPerfTestCase.Count;
            TruncatePerfTestCases();

            #endregion

            #region Extract Unit testcases

            UnitTestCaseExtractor dataCaseExtractor;
            dataCaseExtractor = new UnitTestCaseExtractor(m_ListStatement, globalSettingElement, m_FileNavigator.SelectDataTestCasesRoot());
            ValidateUnitCaseName(dataCaseExtractor.ListCase);
            m_ListDataTestCase.AddRange(dataCaseExtractor.ListCase);
            loadedResultSummary.TotalUnitCaseCount = m_ListDataTestCase.Count;
            TruncateUnitTestCases();

            #endregion
        }



        /// <summary>
        /// Truncate those overflow testcase
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="listCases"></param>
        /// <param name="maxCount"></param>
        private void TruncateTestCases<T>(List<T> listCases, int maxCount)
        {
            if (listCases.Count <= maxCount)
                return;

            int startIndex = maxCount; 
            if (startIndex > -1)
            {
                int length = listCases.Count - startIndex;
                listCases.RemoveRange(startIndex, length);
            }
        }


        private void TruncatePerfTestCases()
        {
            if (RuntimeInfo.GetInstance().IsPerfCaseCountOverflow(m_ListPerfTestCase.Count))
            {
                TruncateTestCases(m_ListPerfTestCase, RuntimeInfo.GetInstance().MaxPerfTestCaseCount);
            }
        }


        private void TruncateUnitTestCases()
        {
            if (RuntimeInfo.GetInstance().IsUnitCaseCountOverflow(m_ListDataTestCase.Count))
            {
                TruncateTestCases(m_ListDataTestCase, RuntimeInfo.GetInstance().MaxUnitTestCaseCount);
            }
        }


        private void ValidateDbConnCfgCount()
        {
            int dbConnCfgCount = m_ListDbConnConfig.Count;
            if (RuntimeInfo.GetInstance().IsDbConnCfgCountOverflow(dbConnCfgCount))
            {
                string fmt = SensitiveMessage.DbConnectionCountOverflowMsgFmt;
                string error = string.Format(fmt, RuntimeInfo.GetInstance().MaxDbConnConfigCount);
                throw new DbSharpException(error);
            }
        }

         


        /// <summary>
        /// check whether alias of ProcedureDeclaration and SqlDeclaration is unique
        /// </summary>
        /// <param name="list"></param>
        private void ValidateStatementAlias(IEnumerable<IStatementDeclaration> list)
        {
            var listNames = new HashSet<string>();
            foreach (var item in list)
            {
                //check whether each of Alias is unique
                if (false == listNames.Add(item.Alias))
                    throw new DuplicateNameException(string.Format("The StatementAlias '{0}' is already present, please rename it.", item.Alias));
            }
        }

        /// <summary>
        /// check whether alais of UnitTest is non-empty and unique
        /// </summary>
        /// <param name="list"></param>
        private void ValidateUnitCaseName(IEnumerable<UnitTestCase> list)
        {
            var listNames = new HashSet<string>();
            foreach (var item in list)
            {
                //check whether each of TestCaseName is unique
                if (false == listNames.Add(item.TestCaseName))
                {
                    string fmt = "The CaseName '{0}' of UnitCase is already present, please rename it.";
                    throw new DuplicateNameException(string.Format(fmt, item.TestCaseName));
                }
            }
        }

        /// <summary>
        /// check whether alais of PerfTest is non-empty and unique
        /// </summary>
        /// <param name="list"></param>
        private void ValidatePerfCaseName(IEnumerable<PerformanceTestCase> list)
        {
            var listNames = new HashSet<string>();
            foreach (var item in list)
            {
                //check whether each of TestCaseName is unique
                if (false == listNames.Add(item.TestCaseName))
                {
                    string fmt="The CaseName '{0}' of PerfCase is already present, please rename it.";
                    throw new DuplicateNameException(string.Format(fmt, item.TestCaseName));
                }
            }
        }


        /// <summary>
        /// When the xml is invalid, this event will be fired
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void XmlValidationEventHandler(object sender, ValidationEventArgs e)
        {
            XmlIsValid = false;
            ValidationError = e.Message;
        }



        /// <summary>
        /// Validate the xml file
        /// </summary>
        /// <param name="schemaUri"></param>
        private void ValidateXmlBySchema1(string schemaUri)
        {
            if (string.IsNullOrEmpty(schemaUri))
                return;

            XDocument doc = XDocument.Load(m_TestCaseFile);
            XmlSchemaSet schemaSet = new XmlSchemaSet();
          
            schemaSet.Add("", schemaUri);
            doc.Validate(schemaSet, XmlValidationEventHandler);
        }

        private void ValidateXmlBySchema(string schemaUri)
        {
            //do not validate in order to be compatiblility of different Scheme edition
            return;
        }
         
       

        #endregion


        #region Public properties/methods

        public string ValidationError { get; private set; }
        public bool XmlIsValid { get; private set; }

        public CaseFileNavigator FileNavigator
        {
            get { return m_FileNavigator; }
        }

        public ReadOnlyCollection<DbConnectionConfig> ListDbConnCofig
        {
            get
            {
                return new ReadOnlyCollection<DbConnectionConfig>(m_ListDbConnConfig);
            }
        }

        public ReadOnlyCollection<PerformanceTestCase> ListPerfTestCase
        {
            get
            {
                return new ReadOnlyCollection<PerformanceTestCase>(m_ListPerfTestCase);
            }
        }

        public ReadOnlyCollection<UnitTestCase> ListUnitTestCase
        {
            get
            {
                return new ReadOnlyCollection<UnitTestCase>(m_ListDataTestCase);
            }
        }

        public ReadOnlyCollection<IStatementDeclaration> ListStatement
        {
            get
            {
                return new ReadOnlyCollection<IStatementDeclaration>(m_ListStatement);
            }
        }

        #endregion
    }
}
